/**
 *  Environment.java
 *
 * @author Somanath
 *
 */

package gamepack;

import java.awt.Rectangle;
import java.awt.Graphics;
import java.util.Vector;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Collections;

import java.util.*;
import java.awt.Color;
import java.util.Enumeration;



/**  All subclasses of Sprite should declare this static member.
 *   public static Vector family = createFamily();
 *
 *   This is required because the family should get created very
 *   early (even before the first instance of the Sprite subclass
 *   is created). The only mechanism is using static variable.
 *   If not this could have been hidden in the startSprite() method.
 * */


public class Environment {


    /* Contains a family of Sprite for every attribute. See
       addFamily() and getFamily for more information.       */
    public static Map<String,Vector<Sprite> > familyOfAttributes = new HashMap<String,Vector<Sprite> >();
    //public static Map<String,Vector<Sprite> > familyOfFamilies = new HashMap<String,Vector<Sprite> >();

	/** Vector of references to all the Sprites arranged as per their z order. */
	public static Vector<Sprite> allSprites = new Vector<Sprite>();

    // Loads the class 'className' if it were not loaded.
    // Note: If you want all the sprite resources to be loaded well in
    // advance before they are used then 2 things must be done.
    // 1. Call touchFamily() so that the class loads in advance
    // 2. Ensure that the family loads all its resources during class load
    //    This can be done by creating a static ref to SpriteResource.
    public static void touchFamily(String className) {
               try {
                   //Class touch = Class.forName(className);		// Orignial code before embedding compiler.			
				   Class touch = Sprite.getSpriteClass(className);				   
				   touch.forName(className,true, Sprite.dynamicClassLoader ); // Did't work either.. it threw ClassNotFoundException
               }
               catch( ExceptionInInitializerError e ) {
                      String s = className + ":ExceptionInInitializerError: " + e.getCause();
                      throw new GamePackException(s);
               }
               catch( LinkageError e ) {
                      String s = className + ":LinkageError: " + e.getMessage() + " " + e.getCause();
                      throw new GamePackException(s);
               }
               catch( ClassNotFoundException e ) {
                      String s = className + ":ClassNotFoundException: " + e.getCause();
                      throw new GamePackException(s);
               }
               catch( Throwable e ) {
                      String s = className + ":Throwable:" + e.getCause();
                      throw new GamePackException( s );
               }
    }

    // Loads all classes with names in 'familyNames' if not loaded.
    public static void touchFamilies(String ... familyNames) {
           for(int i=0;i<familyNames.length;i++) {
                touchFamily(familyNames[i]);
           }
    }

/*	private static Vector<Sprite> createFamily(String familyName) {
           // Todo: get rid of defensive check
           if ( familyOfFamilies.containsKey( familyName ) ) {
              throw new RuntimeException("Cannot create family as Family already exists.");
           }
           Vector<Sprite> vs = new Vector<Sprite>();
           familyOfFamilies.put( familyName, vs );
           return vs;
	}

	public static Vector<Sprite> getFamily(String familyName ) {
	       return getFamily( familyName, true );
	}

    // If the class "familyName" is not loaded, then it will be loaded only if
    // initialize parameter is set to true.
	public static Vector<Sprite> getFamily(String familyName, boolean initialize) {
           if( !familyOfFamilies.containsKey( familyName ) ) {
               // Ok Now that the family does not exist lets try to load the class by touching it.
               try {
                      // Let us touch the class so that it loads up.
                      // During class load the createFamily() method may/may not be called
                      // Eg. it will be called if there is a statement like -
                      // static Vector<Sprite> family = Environment.getFamily(--);
                      if( initialize ) {
                          System.out.println(" HERE: Family " + familyName + " does not exist will try to create it ");
                          Class touch = Class.forName(familyName);
                          if( !familyOfFamilies.containsKey( familyName ) ) {
                          // This means the getFamily() Method was not called during Class load.
                          // Or it may also mean we are here during class load !!!
                          // So lets us create the family explicitly.
                          // Note: If the getFamily() method is called during load then this method gets
                          // called twice but synchonously.
                           System.out.println(" HERE: Creating Family " + familyName );
                           createFamily(familyName);

                          }
                      }else {
                           String s = "Family of type <" + familyName + "> does not exist. ";
                           throw new GamePackException( s );
                      }
               }
               catch( ExceptionInInitializerError e ) {
                      String s = familyName + ":ExceptionInInitializerError: " + e.getCause();
                      throw new GamePackException(s);
               }
               catch( LinkageError e ) {
                      String s = familyName + ":LinkageError: " + e.getCause();
                      throw new GamePackException(s);
               }
               catch( ClassNotFoundException e ) {
                      String s = familyName + ":ClassNotFoundException: " + e.getCause();
                      throw new GamePackException(s);
               }
               catch( Throwable e ) {
                      String s = familyName + ":Throwable:" + e.getCause();
                      throw new GamePackException( s );
               }
           }
           return familyOfFamilies.get( familyName );
	}


    public static Vector[] getFamilies( String ... familyNames) {
           Vector[] retVal = new Vector[ familyNames.length ];
           for( int i=0;i< familyNames.length;i++) {
                retVal[i] = getFamily( familyNames[i] );
           }
           return retVal;
    }
*/
	static {
	       // List of Predefined Attributes
                Environment.createAttribute("PLAYER");
                Environment.createAttribute("ENEMY");
                Environment.createAttribute("BLOCK");
	}

    public static void createAttribute(String attribute_ ) {
           // if attribute_ is not present in foa then add it to foa else return (if already present.
           // iterate thru all sprites and add those sprites of foa[attr] which have this attribute set.

           if ( familyOfAttributes.containsKey( attribute_ ) )
              return;

           Vector<Sprite> vs = new Vector<Sprite>();
           familyOfAttributes.put( attribute_, vs );

      		/*try {
        		for( Enumeration e = allSprites.elements(); e.hasMoreElements(); ) {
		     		Sprite s = ((Sprite)e.nextElement());
		     		if ( s.getAttribute( attribute_ ) ) {
		     		   vs.add( s );
		     		}
      			}
            } catch(NoSuchElementException e) {}
              */
    }
    /**
     *   Section About Attributes : How Atributes work in gamepack?
     *
     *   Sprite has 3 basic methods related to Attributes:
     *   1. getAttribute(attr),   // read method not related familyOfAttributes.
     *   2. setAttribute(attr),   // write method
     *   3. unsetAttribute(attr)  // write method
     *
     *   The write methods use Container familyOfAttributes in a
     *   safe manner. They change contents of container only if
     *   the attribute was present in the container.
     *   The setAttribute may call createAttribute if the attribute
     *   was not present.
     *
     *   Environment has 1 method realted to Attributes:
     *   1. getAttribute(attr)   // read/write
     *
     *   This method will call createAttribute internally if the
     *   attribute is not present.
     */

    public static Vector[] getAttributes(String[] attribute_) {			
		   int attrLen = ( attribute_ == null ) ? 0 : attribute_.length ;
           Vector[] retVec = new Vector [attrLen];		  
           for( int i=0;i<attrLen;i++) {
                retVec[i] = getAttribute( attribute_[i] );
           }
           return retVec;
    }
	
	// overloaded methods in case client code cannot use ...  
	public static Vector[] getAttributes(String a) {
		String[] arr = { a };
		return getAttributes( arr );
	}
	
	public static Vector[] getAttributes(String a, String b) {
		String[] arr = { a, b };
		return getAttributes( arr );
	}
	
	public static Vector[] getAttributes(String a,String b,String c) {
		String[] arr = { a,b,c };
		return getAttributes( arr );
	}

    public static Vector<Sprite> getAttribute(String attribute_) {
           // if present in foa then return it else throw gpException.
           if( !familyOfAttributes.containsKey( attribute_ ) ) {
              // String s = "This attribute <" + attribute_ + "> has not been created. ";
              // throw new GamePackException( s );
              createAttribute( attribute_);
           }
           return familyOfAttributes.get( attribute_ );
    }
	
	

    // To be implemented later.
    // iterate thru all sprites of this family and remove this attribute.
    // (should not make difference if the attribute was a class name
    public static void destroyAttribute(String attribute_) {    }

    	// ----------- All Sprite Killing Routines -----------
    // If killFlag is set, then all Sprites in "vs"  are killed.
    // else all sprites except those in "vs" are killed.
	public static void killSprites(Vector<Sprite> vs, boolean killFlag) {
		if( (Game.verbose & Game.VERBOSE_GENERAL) != 0 )	Game.print("Sprite.kill() {...");

        if ( killFlag ) {
		   while( vs.size() > 0 ) {
		   	 Sprite s = vs.lastElement();
			 s.stopSprite();
		   }
		} else {
		   Vector<Sprite> spritesToBeKilled = new Vector<Sprite>();
		   for( int i=0;i < Environment.allSprites.size(); i++ ) {
		        Sprite s = Environment.allSprites.get(i);
		        if( !vs.contains( s ) ) {
		            spritesToBeKilled.add( s );
		        }
		   }
		   for( int i=0;i < spritesToBeKilled.size(); i++ ) {
		        Sprite s = spritesToBeKilled.get(i);
		        s.stopSprite();
		   }

		}

		if( (Game.verbose & Game.VERBOSE_GENERAL) != 0 )	Game.print("...}");
	}

	public static void killSprites() {
	    killSprites( allSprites, true );
	}

	/** Gets the nearest sprite from Vector "spriteFamily" whose
	    state is valid.
	    @returns null if spriteFamily is null or has no valid sprites */

	public static Sprite getNearest(Sprite spr,Vector spriteFamily) {
		if( spriteFamily == null || spriteFamily.size() == 0 ) return null;

		Sprite nearestSprite = null;
		int minDistance=Integer.MAX_VALUE;

		for( Enumeration e= spriteFamily.elements(); e.hasMoreElements(); ) {
			Sprite s = (Sprite) e.nextElement();
			if( !s.valid() ) continue;
			int dist = ( s.x - spr.x )*( s.x - spr.x ) + ( s.y - spr.y )*( s.y - spr.y );
			if( minDistance > dist ) { nearestSprite = s;  minDistance = dist; }
		}
		return nearestSprite;
	}

}